package com.qf.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.entity.Result;
import com.qf.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class VerifyJWTFilter extends BasicAuthenticationFilter {

    private static final String AUTH_PREFIX = "Bearer ";

    public VerifyJWTFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    public VerifyJWTFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }

    /**
     * 原有逻辑是从session中获取用户信息，如果获取到会通过SecurityContextHolder对象存储，后期使用
     * 我们现在要解析请求头中的JWT，获取用户名和角色权限信息，组织成认证对象，存入SecurityContextHolder
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //获取请求头中的jwt Authorization
        String authorization = request.getHeader("Authorization");
        if (StringUtils.isEmpty(authorization)){
            //放行，一些不需要认证的直接放行。
            chain.doFilter(request,response);
            return;
        }
        if (!authorization.startsWith(AUTH_PREFIX)){
            throw new RuntimeException("认证头信息携带的格式不正确！！！");
        }
        //获取头信息中的jwt串部分
        String jwt = authorization.replaceAll(AUTH_PREFIX,"");

        try {
            //① 面试题：这个串一旦生成，服务器是不存储的，是存储浏览器端
            //如果浏览器端因为用户个人原因，将这个jwt串暴露给了别人。
            //别人就可以一直使用这个jwt串访问我们服务器，操作用户的数据。（只要不过其）

            //在redis中维护一个黑名单。 黑名单可以定期去删除（jwt是有过期时间）
            //先判断当前得到的这个jwt是否在黑名单中，如果在黑名单中，直接告诉用户重新登录
            //如果不再黑名单中，继续往下执行流程。

            //解析jwt
            Claims map = JwtUtils.parseBody(jwt);
            //保存认证信息到上下文对象
            saveContext(map);
            //放行
            chain.doFilter(request,response);
        }catch (ExpiredJwtException expiredJwtException){
            // ② 面试题
            //jwt过期（登录过期）-- 核心思路：如果过期时间较短（2个小时以内），重新生成一个token，允许访问，如果过期时间较长，只能重新登录
            Claims claims = expiredJwtException.getClaims();
            long expireTimeMillis = claims.getExpiration().getTime();
            long nowTimeMillis = System.currentTimeMillis();
            if (nowTimeMillis - expireTimeMillis > (1000*60*60*2)){
                //真的过期了，没救了，只能重新登录
                Result result = Result.noLogin("请先登录");
                ObjectMapper om = new ObjectMapper();
                String json = om.writeValueAsString(result);

                response.setContentType("text/html;charset=utf-8");
                response.getWriter().write(json);
            }else{
                //可以再给一次机会 -- 重新生成一个token
                String newToken = JwtUtils.createToken(claims, claims.getSubject());
                response.setHeader("refresh_token",newToken);
                //这次是登录
                saveContext(claims);
                chain.doFilter(request,response);
            }
        }catch (Exception e){
            //其他问题 -- 未登录 （可能是jwt串被篡改了，或者jwt串不是我们服务器签发的）
            Result result = Result.noLogin("请先登录");
            ObjectMapper om = new ObjectMapper();
            String json = om.writeValueAsString(result);

            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write(json);
        }
    }

    private void saveContext(Claims map) {
        //用户名
        String username = map.getSubject();
        //角色权限
        Collection<GrantedAuthority> authories = new ArrayList<>();
        List list = map.get("roles", List.class);

        for (Object o : list) { // ROLE_P1  ROLE_P2
            GrantedAuthority authority = new SimpleGrantedAuthority((String) o);
            authories.add(authority);
        }

        //解析成功，将用户认证信息存入到SecurityContextHolder，放行
        UsernamePasswordAuthenticationToken uapat = new UsernamePasswordAuthenticationToken(username,null,authories);
        //保存到security的上下文环境中，很重要，后续判断是否登录，主要依据这个对象中是否有认证信息
        SecurityContextHolder.getContext().setAuthentication(uapat);
    }
}
